package com.tgu.home_action.service.utils;

import com.tgu.home_action.pojo.Admin;
import com.tgu.home_action.pojo.Client;
import com.tgu.home_action.pojo.Family;
import com.tgu.home_action.pojo.Volunteer;
import com.tgu.home_action.pojo.exception.RedisTokenErrorException;
import com.tgu.home_action.pojo.exception.RedisTokenNullException;
import com.tgu.home_action.pojo.state.RedisHeader;
import com.tgu.home_action.pojo.state.RoleId;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Service
@Setter
@ConfigurationProperties(prefix = "jwt")
public class JwtService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
    /**
     * token存活时间
     */
    private Long ACCESS_TOKEN_EXPIRATION = 2 * 3600L * 1000;
    /**
     * refreshToken存活时间
     */
    private Long REFRESH_TOKEN_EXPIRATION = 15 * 24 * 3600L * 1000;
    /**
     * jwt所有者
     */
    private String SUBJECT;
    /**
     * jwt签发者
     */
    private String JWT_ISS;
    /**
     * jwt密钥
     */
    @Value("${jwt.secret}")
    private String secret;


    /**
     * 生成accessToken
     *
     * @param userId：用户ID
     * @param role：用户角色
     * @return accessToken
     */
    public String getAccessToken(int userId, String role) {
        Map<String, Object> header = new HashMap<>();
        header.put("alg", "HS256");
        header.put("typ", "JWT");
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("role", role);
        return Jwts.builder().setClaims(claims)
                .setHeader(header)
                .setIssuer(JWT_ISS)
                .setSubject(SUBJECT)
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION))
                .signWith(SIGNATURE_ALGORITHM, secret)
                .compact();
    }

    /**
     * 生成refreshToken
     *
     * @param userId：用户ID
     * @param role：用户角色
     * @return refreshToken
     */
    public String getRefreshToken(int userId, String role) {
        Map<String, Object> header = new HashMap<>();
        header.put("alg", "HS256");
        header.put("typ", "JWT");
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("role", role);
        return Jwts.builder().setClaims(claims)
                .setHeader(header)
                .setIssuer(JWT_ISS)
                .setSubject(SUBJECT)
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + REFRESH_TOKEN_EXPIRATION))
                .signWith(SIGNATURE_ALGORITHM, secret)
                .compact();
    }

    public UserDetails getUserDetails(String token) throws RedisTokenNullException, RedisTokenErrorException {
        Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        int userId = (int) claims.get("userId");
        String role = (String) claims.get("role");
        if (role.equals(RoleId.FAMILY.getRole())) {
            String rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.FAMILY_TOKEN.getHeader() + userId);
            if (rightToken == null) {
                throw new RedisTokenNullException();
            }
            if (!rightToken.equals(token)) {
                rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.FAMILY_OLD_TOKEN.getHeader() + userId);
                if (rightToken == null || !rightToken.equals(token)) {
                    throw new RedisTokenErrorException();
                }
            }
            Family family = new Family(userId);
        } else if (role.equals(RoleId.VOLUNTEER.getRole())) {
            String rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.VOLUNTEER_TOKEN.getHeader() + userId);
            if (rightToken == null) {
                throw new RedisTokenNullException();
            }
            if (!rightToken.equals(token)) {
                rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.VOLUNTEER_OLD_TOKEN.getHeader() + userId);
                if (rightToken == null || !rightToken.equals(token)) {
                    throw new RedisTokenErrorException();
                }
            }
            Volunteer volunteer = new Volunteer(userId);
        } else if (role.equals(RoleId.DISTRICT_ADMIN.getRole()) || role.equals(RoleId.ADMIN.getRole()) || role.equals(RoleId.SUPER_ADMIN.getRole())) {
            String rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.ADMIN_TOKEN.getHeader() + userId);
            if (rightToken == null) {
                throw new RedisTokenNullException();
            }
            if (!rightToken.equals(token)) {
                rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.ADMIN_OLD_TOKEN.getHeader() + userId);
                if (rightToken == null || !rightToken.equals(token)) {
                    throw new RedisTokenErrorException();
                }
            }
            Admin admin = new Admin(userId, role);
        } else if (role.equals("client")) {
            String rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.CLIENT_ACCESS_TOKEN.getHeader() + userId);
            if (rightToken == null) {
                throw new RedisTokenNullException();
            }
            if (!rightToken.equals(token)) {
                throw new RedisTokenErrorException();
            }
            Client client = new Client();
            client.setId(userId);
        } else {
            role = "anonymity";
        }
        return User.withUsername(new String(role + "_" + String.valueOf(userId))).password("123456").roles(role).build();
    }

    /**
     * 解密Token，得到UserDetails
     *
     * @param token：token
     * @param request：httpServletRequest,存储用户信息
     * @return UserDetails
     */
    public UserDetails getUserDetails(String token, HttpServletRequest request) throws RedisTokenNullException, RedisTokenErrorException {
        Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        int userId = (int) claims.get("userId");
        String role = (String) claims.get("role");
        if (role.equals(RoleId.FAMILY.getRole())) {
            String rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.FAMILY_TOKEN.getHeader() + userId);
            if (rightToken == null) {
                throw new RedisTokenNullException();
            }
            if (!rightToken.equals(token)) {
                rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.FAMILY_OLD_TOKEN.getHeader() + userId);
                if (rightToken == null || !rightToken.equals(token)) {
                    throw new RedisTokenErrorException();
                }
            }
            Family family = new Family(userId);
            request.setAttribute("familyUser", family);
        } else if (role.equals(RoleId.VOLUNTEER.getRole())) {
            String rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.VOLUNTEER_TOKEN.getHeader() + userId);
            if (rightToken == null) {
                throw new RedisTokenNullException();
            }
            if (!rightToken.equals(token)) {
                rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.VOLUNTEER_OLD_TOKEN.getHeader() + userId);
                if (rightToken == null || !rightToken.equals(token)) {
                    throw new RedisTokenErrorException();
                }
            }
            Volunteer volunteer = new Volunteer(userId);
            request.setAttribute("volunteerUser", volunteer);
        } else if (role.equals(RoleId.DISTRICT_ADMIN.getRole()) || role.equals(RoleId.ADMIN.getRole()) || role.equals(RoleId.SUPER_ADMIN.getRole())) {
            String rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.ADMIN_TOKEN.getHeader() + userId);
            if (rightToken == null) {
                throw new RedisTokenNullException();
            }
            if (!rightToken.equals(token)) {
                rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.ADMIN_OLD_TOKEN.getHeader() + userId);
                if (rightToken == null || !rightToken.equals(token)) {
                    throw new RedisTokenErrorException();
                }
            }
            Admin admin = new Admin(userId, role);
            request.setAttribute("adminUser", admin);
        } else if (role.equals("client")) {
            String rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.CLIENT_ACCESS_TOKEN.getHeader() + userId);
            if (rightToken == null) {
                throw new RedisTokenNullException();
            }
            if (!rightToken.equals(token)) {
                throw new RedisTokenErrorException();
            }
            Client client = new Client(userId);
            client.setId(userId);
            request.setAttribute("clientUser", client);
        } else {
            role = "anonymity";
        }
        return User.withUsername(role).password("123456").roles(role).build();
    }

    /**
     * 获得用户信息
     *
     * @param token：token
     * @return user
     */
    public Object getUser(String token) {
        Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        int userId = (int) claims.get("userId");
        String role = (String) claims.get("role");
        if (role.equals(RoleId.FAMILY.getRole())) {
            return new Family(userId);
        } else if (role.equals(RoleId.VOLUNTEER.getRole())) {
            return new Volunteer(userId);
        } else if (role.equals(RoleId.DISTRICT_ADMIN.getRole()) || role.equals(RoleId.ADMIN.getRole()) || role.equals(RoleId.SUPER_ADMIN.getRole())) {
            return new Admin(userId, role);
        } else if (role.equals("client")) {
            return new Client(userId);
        } else {
            return null;
        }
    }

    /**
     * 获取UserId
     */
    public int getUserId(String token) throws RedisTokenNullException, RedisTokenErrorException {
        Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        int userId = (int) claims.get("userId");
        String role = (String) claims.get("role");
        if (role.equals(RoleId.FAMILY.getRole())) {
            String rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.FAMILY_TOKEN.getHeader() + userId);
            if (rightToken == null) {
                throw new RedisTokenNullException();
            }
            if (!rightToken.equals(token)) {
                throw new RedisTokenErrorException();
            }
        } else if (role.equals(RoleId.VOLUNTEER.getRole())) {
            String rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.VOLUNTEER_TOKEN.getHeader() + userId);
            if (rightToken == null) {
                throw new RedisTokenNullException();
            }
            if (!rightToken.equals(token)) {
                throw new RedisTokenErrorException();
            }
        } else if (role.equals(RoleId.DISTRICT_ADMIN.getRole()) || role.equals(RoleId.ADMIN.getRole()) || role.equals(RoleId.SUPER_ADMIN.getRole())) {
            String rightToken = stringRedisTemplate.opsForValue().get(RedisHeader.ADMIN_TOKEN.getHeader() + userId);
            if (rightToken == null) {
                throw new RedisTokenNullException();
            }
            if (!rightToken.equals(token)) {
                throw new RedisTokenErrorException();
            }
        } else {
            throw new RedisTokenErrorException();
        }
        return userId;
    }

}
